﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MyLib.Data;
using System.IO;
using System.Text;

namespace MyLib.Web {
	/// <summary>
	/// 
	/// </summary>
	public static class HttpContextExtensions {
		private static log4net.ILog m_logger = log4net.LogManager.GetLogger("HttpContext");

		private const string ConstCodeFileName = @"~/ConstSysCode.xml";

		private const string ErrorMessagesKey = @"ErrorMessages";

		private const string WarnMessagesKey = @"WarnMessages";

		private const string InfoMessagesKey = @"InfoMessages";

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="errorMessage"></param>
		public static void AddError(this HttpContext context, string errorMessage) {
			var errorMessages = context.Items[ErrorMessagesKey] as IList<string>;

			if (errorMessages == null) {
				errorMessages = new List<string>();
				context.Items[ErrorMessagesKey] = errorMessages;
			}

			errorMessages.Add(errorMessage);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="format"></param>
		/// <param name="args"></param>
		public static void AddError(this HttpContext context, string format, params object[] args) {
			if (string.IsNullOrWhiteSpace(format))
				return;
			else
				AddError(context, string.Format(format, args));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="warningMessage"></param>
		public static void AddWarning(this HttpContext context, string warningMessage) {
			var warnMessages = context.Items[WarnMessagesKey] as IList<string>;

			if (warnMessages == null) {
				warnMessages = new List<string>();
				context.Items[WarnMessagesKey] = warnMessages;
			}

			warnMessages.Add(warningMessage);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string[] GetErrors(this HttpContext context) {
			var errorMessages = context.Items[ErrorMessagesKey] as IList<string>;

			if (errorMessages == null)
				return new string[0];
			else
				return errorMessages.ToArray();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="format"></param>
		/// <param name="args"></param>
		public static void AddWarning(this HttpContext context, string format, params object[] args) {
			if (string.IsNullOrWhiteSpace(format))
				return;
			else
				AddWarning(context, string.Format(format, args));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string[] GetWarns(this HttpContext context) {
			var warnMessages = context.Items[WarnMessagesKey] as IList<string>;

			if (warnMessages == null)
				return new string[0];
			else
				return warnMessages.ToArray();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="infoMessage"></param>
		public static void AddInformation(this HttpContext context, string infoMessage) {
			var infoMessages = context.Items[InfoMessagesKey] as IList<string>;

			if (infoMessages == null) {
				infoMessages = new List<string>();
				context.Items[InfoMessagesKey] = infoMessages;
			}

			infoMessages.Add(infoMessage);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="format"></param>
		/// <param name="args"></param>
		public static void AddInformation(this HttpContext context, string format, params object[] args) {
			if (string.IsNullOrWhiteSpace(format))
				return;
			else
				AddInformation(context, string.Format(format, args));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string[] GetInformations(this HttpContext context) {
			var infoMessages = context.Items[InfoMessagesKey] as IList<string>;

			if (infoMessages == null)
				return new string[0];
			else
				return infoMessages.ToArray();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="action"></param>
		public static void ExecuteSafely(this HttpContext context, Action action) {
			ExecuteSafely(
				context,
				action,
				(ex) => {
					m_logger.Error("Failed to execute action.", ex);

					AddError(context, ex.FormatMessage());
				});
		}

#pragma warning disable 0618
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void ExecuteSafely(this HttpContext context, Action action, Action<Exception> exceptionHandler) {
			try {
				action();
			} catch (ApplicationException ex) {
				exceptionHandler(ex);
			} catch (AccessDeniedHttpException adex) {
				exceptionHandler(adex);
			}
		}
#pragma warning restore 0618

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteDownloadable(this HttpContext context, string fileName, Action<TextWriter> action, Action<TextWriter, Exception> exceptionHandler = null) {
			WriteDownloadable(context, fileName, 0L, action, exceptionHandler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteDownloadable(this HttpContext context, string fileName, long size, Action<TextWriter> action, Action<TextWriter, Exception> exceptionHandler = null) {
			Action<Stream> caction = (s) => {
				using (var sw = new System.IO.StreamWriter(s)) {
					action(sw);
				}
			};

			Action<Stream, Exception> ceHandler = null;
			if (exceptionHandler != null) {
				ceHandler = (s, e) => {
					using (var sw = new System.IO.StreamWriter(s)) {
						exceptionHandler(sw, e);
					}
				};
			}

			WriteDownloadable(context, fileName, size, caction, ceHandler);
		}

		/// <summary>
		/// Write downloadable file to response.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteDownloadable(this HttpContext context, string fileName, Action<Stream> action, Action<Stream, Exception> exceptionHandler = null) {
			WriteDownloadable(context, fileName, 0L, action, exceptionHandler);
		}

		/// <summary>
		/// Write downloadable file to response.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteDownloadable(this HttpContext context, string fileName, long size, Action<Stream> action, Action<Stream, Exception> exceptionHandler = null) {
			WriteFile(context, fileName, size, action, true, DateTime.Now, exceptionHandler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteFile(this HttpContext context, string fileName, Action<TextWriter> action, Action<TextWriter, Exception> exceptionHandler = null) {
			WriteFile(context, fileName, 0L, action, exceptionHandler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteFile(this HttpContext context, string fileName, long size, Action<TextWriter> action, Action<TextWriter, Exception> exceptionHandler = null) {
			Action<Stream> caction = (s) => {
				using (var sw = new System.IO.StreamWriter(s)) {
					action(sw);
				}
			};

			Action<Stream, Exception> ceHandler = null;
			if (exceptionHandler != null) {
				ceHandler = (s, e) => {
					using (var sw = new System.IO.StreamWriter(s)) {
						exceptionHandler(sw, e);
					}
				};
			}

			WriteFile(context, fileName, size, caction, ceHandler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteFile(this HttpContext context, string fileName, Action<Stream> action, Action<Stream, Exception> exceptionHandler = null) {
			WriteFile(context, fileName, 0L, action, exceptionHandler);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="action"></param>
		/// <param name="exceptionHandler"></param>
		public static void WriteFile(this HttpContext context, string fileName, long size, Action<Stream> action, Action<Stream, Exception> exceptionHandler = null) {
			WriteFile(context, fileName, size, action, false, DateTime.Now, exceptionHandler);
		}

		/// <summary>
		/// Write file to response.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="action"></param>
		/// <param name="isAttachment"></param>
		/// <param name="date"></param>
		/// <param name="exceptionHandler"></param>
		private static void WriteFile(this HttpContext context, string fileName, long size, Action<Stream> action, bool isAttachment, DateTime date, Action<Stream, Exception> exceptionHandler) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(fileName));

			if (context == null)
				return;

			context.Response.ClearHeaders();
			context.Response.Clear();

			fileName = RegularDownloadableFileName(fileName);

			var contentType = GetContentTypeByFileName(fileName);
			if (contentType.StartsWith("text/")) {
				contentType += ";charset=utf-8";
			}

			fileName = EncodeDownloadableFileName(fileName, context);

			context.Response.AppendHeader("Content-Type", contentType);
			context.Response.AppendHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
			if (size != 0L) {
				context.Response.AppendHeader("Content-Length", size.ToString());
			}
			context.Response.AppendHeader("Date", date.ToUniversalTime().ToString("R"));

			try {
				action(context.Response.OutputStream);
			} catch (Exception e) {
				m_logger.Warn("Cannot write file to response.", e);

				if (exceptionHandler == null) {
					exceptionHandler = WriteExceptionFile;
				}

				context.Response.ClearHeaders();
				context.Response.Clear();
				context.Response.AppendHeader("Content-Type", "text/plain;charset=utf-8");
				context.Response.AppendHeader("Content-Disposition", "attachment; filename=\"error.txt\"");
				context.Response.AppendHeader("Date", new DateTime().ToUniversalTime().ToString("R"));

				exceptionHandler(context.Response.OutputStream, e);
			}

			context.Response.End();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="stream"></param>
		/// <param name="e"></param>
		private static void WriteExceptionFile(Stream stream, Exception e) {
			System.Diagnostics.Debug.Assert(e != null);

			var bytes = Encoding.UTF8.GetBytes(e.Message);

			stream.Write(bytes, 0, bytes.Length);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private static string RegularDownloadableFileName(string fileName) {
			var ics = Path.GetInvalidFileNameChars();

			var buf = new char[fileName.Length];
			var j = 0;
			for (int i = 0; i < fileName.Length; ++i) {
				var c = fileName[i];

				if (ics.Contains(c))
					continue;

				buf[j++] = c;
			}

			return new string(buf, 0, j);
		}

		/// <summary>
		/// Encode file-name for download
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="context"></param>
		/// <returns></returns>
		private static string EncodeDownloadableFileName(string fileName, HttpContext context) {
			// IE bug
			const int IE_BUG_HEADER_MAX_LENGTH = 150;

			bool isIE = false;
			bool isFirefox = false;

			var browser = context.Request.Browser;
			if (!string.IsNullOrEmpty(browser.Browser)) {
				isIE = browser.Browser == "IE";
				isFirefox = browser.Browser == "Firefox";
			}

			string encodedFileName = string.Empty;
			if (isIE) {
				// Use Url.Encode method for IE.
				encodedFileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);
				if (encodedFileName.EndsWith("%0A")) {
					encodedFileName = encodedFileName.Substring(0, encodedFileName.Length - 3);
				}

				var ieVersion = browser.MajorVersion * 100 + browser.MinorVersion;

				if (encodedFileName.Length > IE_BUG_HEADER_MAX_LENGTH
					&& ieVersion < 601 /*IE 6.1(SP1)*/) {
					// Too long.
					string ext = System.IO.Path.GetExtension(fileName);

					int limit = IE_BUG_HEADER_MAX_LENGTH - ext.Length;

					var mainFileName
						= System.IO.Path.GetFileNameWithoutExtension(fileName);

					// Truncate file-name.
					mainFileName = mainFileName
						.Substring(0, Math.Min(mainFileName.Length, limit / 9));

					// Re-encode file-name.
					encodedFileName = HttpUtility.UrlEncode(mainFileName + ext, System.Text.Encoding.UTF8);
					if (encodedFileName.EndsWith("%0A")) {
						encodedFileName = encodedFileName.Substring(0, encodedFileName.Length - 3);
					}
				}
			} else if (isFirefox) {
				// Use Base64 for RFC.
				encodedFileName = "=?UTF-8?B?" + EncodeToBase64(fileName) + "?=";
			} else {
				encodedFileName = fileName;
			}

			return encodedFileName;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="src"></param>
		/// <param name="encoding"></param>
		/// <returns></returns>
		private static string EncodeToBase64(string src, Encoding encoding = null) {
			if (encoding == null)
				encoding = Encoding.UTF8;

			var bytes = encoding.GetBytes(src);

			try {
				return Convert.ToBase64String(bytes, Base64FormattingOptions.None);
			} catch {
				return string.Empty;
			}
		} // end of EncodeToBase64.

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public static string GetContentTypeByFileName(string fileName) {
			var ext = System.IO.Path.GetExtension(fileName).ToUpper();
			if (ext == ".GIF") {
				return "image/gif";
			} else if (ext == ".PNG") {
				return "image/png";
			} else if (ext == ".JPG") {
				return "image/jpeg";
			} else if (ext == ".JPEG") {
				return "image/jpeg";
			} else if (ext == ".BMP") {
				return "image/bitmap";
			} else if (ext == ".GIF") {
				return "image/gif";
			} else if (ext == ".ICO") {
				return "image/x-icon";
			} else if (ext == ".ICON") {
				return "image/x-icon";
			} else if (ext == ".TXT") {
				return "text/plain";
			} else if (ext == ".DOC") {
				return "application/msword";
			} else if (ext == ".PDF") {
				return "application/pdf";
			} else if (ext == ".XLS") {
				return @"application/vnd.ms-excel";
			} else if (ext == ".DOCX") {
				return @"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
			} else if (ext == ".XLSX") {
				return @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			} else if (ext == ".ZIP") {
				return @"application/zip";
			} else if (ext == ".RAR") {
				return @"application/x-rar-compressed";
			} else {
				return @"application/octet-stream";
			}
		} // end of GetContentTypeByFileName.

		/// <summary>
		/// 
		/// </summary>
		internal const string CookieLoginName = ".AUTH_LOGIN_NAME";

		/// <summary>
		/// 
		/// </summary>
		internal const string CookieRealName = ".AUTH_REAL_NAME";

		/// <summary>
		/// 
		/// </summary>
		internal const string CookieGroupId = ".AUTH_GROUP_ID";

		/// <summary>
		/// 
		/// </summary>
		internal const string CookieExtPrefix = ".AUTH_EXT_";

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string GetUserLoginName(this HttpContext context) {
			System.Diagnostics.Debug.Assert(context != null);

			var cookie = context.Request.Cookies[CookieLoginName];
			if (cookie != null) {
				return HttpUtility.UrlDecode(cookie.Value);
			} else {
				return string.Empty;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string GetUserRealName(this HttpContext context) {
			System.Diagnostics.Debug.Assert(context != null);

			var cookie = context.Request.Cookies[CookieRealName];
			if (cookie != null) {
				return HttpUtility.UrlDecode(cookie.Value);
			} else {
				return string.Empty;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <returns></returns>
		public static string GetUserGroupId(this HttpContext context) {
			System.Diagnostics.Debug.Assert(context != null);

			var cookie = context.Request.Cookies[CookieGroupId];
			if (cookie != null) {
				return HttpUtility.UrlDecode(cookie.Value);
			} else {
				return string.Empty;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="extIdArray"></param>
		/// <returns></returns>
		public static IEnumerable<string> GetUserExt(this HttpContext context, params int[] extIdArray) {
			System.Diagnostics.Debug.Assert(context != null);

			foreach (var extId in extIdArray) {
				var cookie = context.Request.Cookies[CookieExtPrefix + extId];
				if (cookie != null) {
					yield return HttpUtility.UrlDecode(cookie.Value);
				} else {
					yield return string.Empty;
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="categoryId"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public static string GetCodeTextFromValue(this HttpContext context, int categoryId, object value) {
			return GetCodeTextFromValue(context, ConstCodeFileName, categoryId, value);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="codeFileName"></param>
		/// <param name="categoryId"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public static string GetCodeTextFromValue(this HttpContext context, string codeFileName, int categoryId, object value) {
			System.Diagnostics.Debug.Assert(codeFileName != null);

			codeFileName = codeFileName.Trim();

			if (!codeFileName.StartsWith("~/") && !System.IO.Path.IsPathRooted(codeFileName)) {
				codeFileName = "~/" + codeFileName;
			}

			var cp = new CodeFileProvider(context.Request.MapPath(codeFileName), new WebCacheProvider(context));
			return cp.GetTextFromValue(categoryId, value);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="categoryId"></param>
		/// <returns></returns>
		public static IEnumerable<Tuple<string, string, string>> GetCodesFromCategory(this HttpContext context, int categoryId) {
			return GetCodesFromCategory(context, ConstCodeFileName, categoryId);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="codeFileName"></param>
		/// <param name="categoryId"></param>
		/// <returns></returns>
		public static IEnumerable<Tuple<string, string, string>> GetCodesFromCategory(this HttpContext context, string codeFileName, int categoryId) {
			System.Diagnostics.Debug.Assert(codeFileName != null);

			codeFileName = codeFileName.Trim();

			if (!codeFileName.StartsWith("~/") && !System.IO.Path.IsPathRooted(codeFileName)) {
				codeFileName = "~/" + codeFileName;
			}

			var cp = new CodeFileProvider(context.Request.MapPath(codeFileName), new WebCacheProvider(context));

			return cp.GetCodesFromCategory(categoryId);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="group"></param>
		/// <returns></returns>
		public static string[] GetCheckedValues(this HttpContext context, string group) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(group));

			var checkedValues = context.Request[group];
			if (string.IsNullOrEmpty(checkedValues))
				return new string[0];
			else
				return checkedValues.Split(',');
		}
	} // end of HttpContextExtensions.
}
